﻿#pragma warning(disable:4996)  //是为了屏蔽掉错误代码为4996的错误
#define NODE_WIDTH 40    //定义的是标识符常量

#include <easyx.h>
#include <stdio.h>
#include <Conio.h>  //引入两个头文件，一个是_kbhit()的头文件，一个是time函数的头文件
#include <time.h>

enum direction {    //定义的枚举变量，是表达出贪吃蛇向哪移动的方向
	eUp,     //向上
	eDown,   //向下
	eLeft,   //向左
	eRight   //向右
};

typedef struct {   //贪吃蛇的结点
	int x;
	int y;
}node;

void printline()   //进行表格的绘画
{
	int x = 0;
	for (x = 0; x <= 800; x += NODE_WIDTH)
	{
		line(x, 0, x, 600);   //line函数是画出直线的函数
	}
	int y = 0;
	for (y = 0; y < 600; y += NODE_WIDTH)
	{
		line(0, y, 800, y);
	}
}

void printsnack(node* snack, int n)  //进行蛇主题的绘画
{
	int left, right, bottom, top;
	int i = 0;
	for (i = 0; i < n; i++)
	{
		left = snack[i].x * NODE_WIDTH;
		top = snack[i].y * NODE_WIDTH;
		right = (snack[i].x + 1) * NODE_WIDTH;
		bottom = (snack[i].y + 1) * NODE_WIDTH;
		solidrectangle(left, top, right, bottom); 
	}
}

node snackmove(node* snack, int length, int direction) //蛇移动的函数
{
	node newfinsh = snack[length - 1];  //将蛇结点的最后一个结点保存下来
	for (int i = length - 1; i > 0; i--)
	{
		snack[i] = snack[i - 1];   //将前一个蛇结点移动到后一个蛇结点，进行蛇的移动
	}
	node head;  //定义一个头结点并利用头结点进行方向的移动
	head = snack[0];
	if (direction == eUp)
	{
		head.y--;
	}
	else if (direction == eDown)
	{
		head.y++;
	}
	else if (direction == eLeft)
	{
		head.x--;
	}
	else if (direction == eRight)
	{
		head.x++;
	}
	snack[0] = head;
	return newfinsh;
}

void changedirection(enum direction* pD)   //中间有键盘交互的代码
{ 
	if (_kbhit() != 0)//检查输入缓存区中是否有数据
	{
		char c = _getch();//从缓存区中获取输入数据并做相应的数据
		switch (c)
		{
		case 'w':
			if(*pD != eDown) //要注意蛇不能向正在移动的反方向进行移动
			*pD = eUp;
			break;
		case 's':
			if(*pD != eUp)
			*pD = eDown;
			break;
		case 'a':
			if(*pD != eRight)
			*pD = eLeft;
			break;
		case 'd':
			if(*pD != eLeft)
			*pD = eRight;
			break;
		}
	}
}

node creatfood(node* snack, int length)  //创建食物
{
	node food;
	while (1)
	{
		food.x = rand() % (800 / NODE_WIDTH); //利用随机数进行食物位置的随机
		food.y = rand() % (600 / NODE_WIDTH);
		int i = 0;
		for (i = 0; i < length; i++) //利用for循环一一遍历，防止食物生成在蛇身上
		{
			if (food.x == snack[i].x && food.y == snack[i].y)
			{
				break;
			}
		}
		if (i < length) //如果i小于蛇的长度，则需要继续遍历，知道i大于蛇的长度
		{
			continue;
		}
		else
			break;
	}
	return food;
}

void printfood(node food)  //在面板上打印出食物
{
	int left, right, top, down;
	left = food.x * NODE_WIDTH;
	top = food.y * NODE_WIDTH;
	right = (food.x + 1) * NODE_WIDTH;
	down = (food.y + 1) * NODE_WIDTH;
	setfillcolor(YELLOW);
	solidrectangle(left, top, right, down);
	setfillcolor(WHITE);
}

bool isgameover(node* snack, int length)//游戏结束的条件
{
	if (snack[0].x < 0 || snack[0].x>800 / NODE_WIDTH)//当蛇碰到墙壁，游戏结束
		return true;
	if (snack[0].y < 0 || snack[0].y>600 / NODE_WIDTH)
		return true;
	for (int i = 1; i < length; i++)   //当蛇碰到蛇身游戏结束
	{
		if (snack[0].x == snack[i].x && snack[0].y == snack[i].y)
			return true;
	}
	return false;
}

void reset(node* snack, int* length, enum direction* d)//重新进行初始化
{
	snack[0] = node{ 5,7 };
	snack[1] = node{ 4,7 };
	snack[2] = node{ 3,7 };
	snack[3] = node{ 2,7 };
	snack[4] = node{ 1,7 };
	*length = 5;
	*d = eRight;
}

int main()
{
    //创建窗口
	initgraph(800, 600);
	setbkcolor(RGB(164, 225, 202)); //创建一个像素窗口
	cleardevice();
	//定义变量
	srand(unsigned int(time(NULL))); //随着时间而改变
	int length = 5;
	enum direction d = eRight;
	node snack[100] = { {5, 7}, {4, 7}, {3, 7}, {2, 7}, {1, 7} };
	node food = creatfood(snack, length);
	//使游戏循环
	while (1)
	{
		cleardevice();
		printline();
		printsnack(snack, length);
		printfood(food);
		Sleep(500);
		changedirection(&d);
		node newlist = snackmove(snack, length, d);
		if (snack[0].x == food.x && snack[0].y == food.y)
		{
			if(length < 100)
			{
				snack[length] = newlist;
				length++;
			}
			food = creatfood(snack, length);
		}
		if (isgameover(snack, length) == true)
		{
			reset(snack, &length, &d);
			food = creatfood(snack, length);
		}
	}
	//使窗口卡顿
	getchar();
	closegraph();
	return 0;
}